package web

import (
	"fmt"
	"gitee.com/yimtune/webook/internal/service"
	"gitee.com/yimtune/webook/internal/service/oauth2/wechat"
	ijwt "gitee.com/yimtune/webook/internal/web/jwt"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	uuid "github.com/lithammer/shortuuid/v4"
	"net/http"
)

type OAuth2WechatHandler struct {
	svc     wechat.Service      // 接口
	userSvc service.UserService // 接口  //需要用到 UserService 接口
	ijwt.Handler
	key             []byte // jwt key
	stateCookieName string // state 逻辑为什么放到 Handler中，而不是放到 svc 层 VerifyCode 中 ？ 和外部交互时安全的一部分 不属于业务逻辑
}

func NewOAuth2WechatHandler(svc wechat.Service,
	hdl ijwt.Handler,
	userSvc service.UserService) *OAuth2WechatHandler {
	return &OAuth2WechatHandler{
		svc:             svc,
		userSvc:         userSvc,
		key:             []byte("k6CswdUm77WKcbM68UQUuxVsHSpTCwgB"),
		stateCookieName: "jwt-state", // cookie 中存一个jwt  里面的数据是 sate 用于防止state被篡改
		Handler:         hdl,
	}
}

func (o *OAuth2WechatHandler) RegisterRoutes(server *gin.Engine) {
	g := server.Group("/oauth2/wechat")
	g.GET("/authurl", o.Auth2URL)
	g.Any("/callback", o.Callback)
}

func (o *OAuth2WechatHandler) Auth2URL(ctx *gin.Context) {
	state := uuid.New() // 生成一个短uuid 返回给微信
	val, err := o.svc.AuthURL(ctx, state)
	if err != nil {
		ctx.JSON(http.StatusOK, Result{
			Msg:  "构造跳转URL失败",
			Code: 5,
		})
		return
	}
	// 用state生成一个jwt放入cookie中，
	err = o.setStateCookie(ctx, state)
	if err != nil {
		ctx.JSON(http.StatusOK, Result{
			Msg:  "服务器异常",
			Code: 5,
		})
	}

	ctx.JSON(http.StatusOK, Result{
		Data: val,
	})
}

func (o *OAuth2WechatHandler) Callback(ctx *gin.Context) {

	err := o.verifyState(ctx) // 先校验 state
	if err != nil {
		ctx.JSON(http.StatusOK, Result{
			Msg:  "非法请求",
			Code: 4,
		})
		return
	}

	code := ctx.Query("code")
	wechatInfo, err := o.svc.VerifyCode(ctx, code)
	if err != nil {
		ctx.JSON(http.StatusOK, Result{
			Msg:  "授权码有误",
			Code: 4,
		})
		return
	}
	fmt.Println(wechatInfo.UnionId)
	u, err := o.userSvc.FindOrCreateByWechat(ctx, wechatInfo)
	if err != nil {
		ctx.JSON(http.StatusOK, Result{
			Msg:  "系统错误",
			Code: 5,
		})
		return
	}
	err = o.SetLoginToken(ctx, u.Id)
	if err != nil {
		ctx.String(http.StatusOK, "系统错误")
		return
	}
	ctx.JSON(http.StatusOK, Result{
		Msg: "OK",
	})
	// return // 不需要加 return
}

func (o *OAuth2WechatHandler) verifyState(ctx *gin.Context) error {
	state := ctx.Query("state")              //  获取url 参数中 state
	ck, err := ctx.Cookie(o.stateCookieName) // 获取 jwt-state 里面存的 jwt
	if err != nil {
		return fmt.Errorf("无法获得 cookie %w", err)
	}
	// jwt 校验
	var sc StateClaims
	_, err = jwt.ParseWithClaims(ck, &sc, func(token *jwt.Token) (interface{}, error) {
		return o.key, nil
	})
	if err != nil {
		return fmt.Errorf("解析 token 失败 %w", err)
	}
	// 比较jwt 和 url 中的 state 是否一致
	if state != sc.State {
		// state 不匹配，有人搞你
		return fmt.Errorf("state 不匹配")
	}
	return nil
}

func (o *OAuth2WechatHandler) setStateCookie(ctx *gin.Context,
	// 设置返回头cookie  Set-Cookie:jwt-state=jwt-token
	state string) error {
	claims := StateClaims{
		State: state,
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS512, claims)
	tokenStr, err := token.SignedString(o.key)
	if err != nil {

		return err
	}
	ctx.SetCookie(o.stateCookieName, tokenStr, //为什么放到cookie？ cookie 可以自动带过来  微信跳回来直接到后端  没有到前端
		600, "/oauth2/wechat/callback",
		"", false, true)
	return nil
}

// StateClaims 生成一个jwt 用做state
type StateClaims struct {
	jwt.RegisteredClaims
	State string
}

// 验证 state 是否被篡改防止 跨站请求伪造攻击
